/*******************************************************************************
 * Copyright (c) 2011 TXT e-solutions SpA
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * This work was performed within the IoT_at_Work Project
 * and partially funded by the European Commission's
 * 7th Framework Programme under the research area ICT-2009.1.3
 * Internet of Things and enterprise environments.
 *
 *
 * Authors:
 *     Cristoforo Seccia (TXT e-solutions SpA)
 *
 * Contributors:
 *      Domenico Rotondi (TXT e-solutions SpA)
 *******************************************************************************/
package it.txt.access.capability.demo.service.impl;

import it.txt.access.capability.commons.schema.validation.CapabilitySchemaValidationHandler;
import it.txt.access.capability.commons.schema.validation.CapabilitySchemaValidationHandlerException;
import it.txt.access.capability.commons.utils.CalendarUtils;
import it.txt.access.capability.commons.utils.XMLPrinter;
import it.txt.access.capability.commons.utils.ClientServerUtils;
import it.txt.access.capability.commons.signer.model.X509CertificateSubjectInfo;
import it.txt.access.capability.demo.schema.CapabilityRequestType;
import it.txt.access.capability.demo.schema.request.factory.CapabilityDemoRequestFactory;
import it.txt.access.capability.demo.schema.factory.CapabilityDemoFactoryException;
import it.txt.access.capability.demo.service.model.ServiceRequestInfoModel;
import it.txt.access.capability.schema.AccessRightType;
import it.txt.access.capability.schema.AccessRightsCapabilityType;
import it.txt.access.capability.verifier.CapabilityVerifier;
import it.txt.access.capability.verifier.CapabilityVerifierException;
import it.txt.access.capability.verifier.VerifiedCapability;
import it.txt.access.capability.commons.signer.X509DocumentSigner;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.datatype.XMLGregorianCalendar;
import org.w3c.dom.Document;

/**
 *
 * @author Cristoforo Seccia (TXT e-solutions SpA)
 */
public class CapabilityAuthDemoServiceHelper {

    private static final Logger LOGGER = Logger.getLogger(CapabilityAuthDemoServiceHelper.class.getName());

    //Used by the server into the xml response if the specified resource id is not the same as
    //the one specified in the capability.
    private static final String RESPONSE_INVALID_RESOURCE_ID = "The Resoure specified in the "
                    + "request do not match the Capability Resource ID.";

    //Used by the server into the xml response if the specified operation is not contained in
    //the action rights of the capability.
    private static final String RESPONSE_INVALID_CAPABILITY_OPERATION = "The Operation specified in the "
                    + "request is not specified in the Capability.";

    //Used by the server into the xml response if the operation is contained within the capability but
    //is not contained in the well known crud operation list.
    private static final String RESPONSE_INVALID_CRUD_OPERATION = "cannot perform the operation "
            + "specified in the request becouse it is not a standard CRUD operations.";

    private static final String RESPONSE_OPERATION_RESOURCE_NOT_EXIST = "The operation "
            + "cannot be performed becouse the file do not exist.";

    //Used by the server after the RESPONSE_CAPABILITY_VALID if the operation Create cannot be performed.
    private static final String RESPONSE_OPERATION_CREATE_ALREADY_EXIST = "The Create operation "
            + "cannot be performed becouse the file already exist.";

    //Used by the server when creating a new file as specified by the capability request.
    private static final String OPERATION_CREATE_FILE_CONTENT = "This file was created by the "
            + "TXT Capability-Based Authorization System in order to perfom the Create operation.";

    //Used by the server after the RESPONSE_CAPABILITY_VALID if the operation Delete cannot be performed.
    private static final String RESPONSE_OPERATION_DELETE_NOT_PERFORMED = "The Delete operation "
            + "cannot be performed becouse the resource is not a file or is not an empty directory.";

    private static final String RESPONSE_OPERATION_CREATE_NOT_PERFORMED = "The Create operation "
            + "cannot be performed.";
    
    private static final String RESPONSE_OPERATION_CREATE_DIRECTORY = "The CREATE operation cannot be perfomed on a directory";

    public static ServiceRequestInfoModel checkRequestCapability(CapabilityRequestType crt) throws CapabilityAuthDemoRequestException {
        ServiceRequestInfoModel dataModel = new ServiceRequestInfoModel();
        X509CertificateSubjectInfo requestSubjectInfo = new X509CertificateSubjectInfo();
        try {
            Document requestDoc = CapabilityDemoRequestFactory.getInstance().createRequestDocument(crt);
            XMLPrinter.logDocumentElement(requestDoc.getDocumentElement(), "Received Capability Request", false);
            // Check the Capability Request has not been tampered!
            if (!X509DocumentSigner.verifyXMLElementSign(requestDoc.getDocumentElement(), requestSubjectInfo)) {
                throw new CapabilityAuthDemoRequestException("The Request Document Sign is Invalid");
            }
        }
        catch (CapabilityDemoFactoryException ex) {
            throw new CapabilityAuthDemoRequestException("Error while recovering Request document.", ex);
        }
        catch (GeneralSecurityException ex) {
            throw new CapabilityAuthDemoRequestException("The Request Document Sign is Invalid.", ex);
        }
        //Recover the capability from the request object
        AccessRightsCapabilityType capability =
                crt.getRequestAccessRightsCapability().getAccessRightsCapability();
        try {
            //Verify the capability through the CapabilityVerifier
            ArrayList<VerifiedCapability> vcs = new ArrayList<VerifiedCapability>();
            if (!CapabilityVerifier.accessRightsCapabilityValidityCheck(capability, vcs)) {
                String message = "Access Rights Capability is well formed but "
                        + "did not pass the validity check.";
                throw new CapabilityAuthDemoRequestException(message);
            }
        } catch (CapabilityVerifierException ex) {
            throw new CapabilityAuthDemoRequestException(ex.getMessage(), ex);
        } catch (CapabilitySchemaValidationHandlerException ex) {
            String errors = CapabilitySchemaValidationHandler.dumpValidationEvents(ex.getValidationEvents());
            throw new CapabilityAuthDemoRequestException(errors, ex);
        }
        //Check that the signer of the request is the subject of the capability
        String requestSubject = requestSubjectInfo.getX509SubjectName();
        requestSubject = requestSubject.trim().replace(" ", "");
        requestSubject = requestSubject.toUpperCase();

        String capabilitySubject = capability.getSubject().getSubjectID().getValue();
        capabilitySubject = capabilitySubject.trim().replace(" ", "");
        capabilitySubject = capabilitySubject.toUpperCase();

        if(!requestSubject.contains(capabilitySubject)){
            String message = "The Subject used to sign the request is not "
                    + "equal to the subject specified in the Capability.";
            throw new CapabilityAuthDemoRequestException(message);
        }
        //Check thet the request issue time is included in the capability
        //validation time range.
        XMLGregorianCalendar requestIssueTime = crt.getRequestIstant();
        XMLGregorianCalendar capabilityNotBeforeTime = capability.getValidityCondition().getNotBefore();
        XMLGregorianCalendar capabilityNotOrOnAfterTime = capability.getValidityCondition().getNotOnOrAfter();
        int checkRequestIssueTimeToNotBeforeTime = CalendarUtils.comapareDataToAnotherDate(requestIssueTime, capabilityNotBeforeTime);
        int checkRequestIssueTimeToNotOrOnAfterTime = CalendarUtils.comapareDataToAnotherDate(requestIssueTime, capabilityNotOrOnAfterTime);
        if(checkRequestIssueTimeToNotBeforeTime != CalendarUtils.AFTER_THAN){
            String message = "The request issue time is not 'after' "
                    + "the Capability 'not before' time.";
            throw new CapabilityAuthDemoRequestException(message);
        }
        if(checkRequestIssueTimeToNotOrOnAfterTime != CalendarUtils.BEFORE_THAN){
            String message = "The request issue time is not 'before' "
                    + "the Capability 'not or on after' time.";
            throw new CapabilityAuthDemoRequestException(message);
        }
        //Everything is fine, now get the model        
        dataModel.setRequestCapability(capability);
        dataModel.setRequestID(crt.getCapabilityRequestID());
        dataModel.setResourceID(crt.getResourceID());
        dataModel.setOperation(crt.getOperation());
        dataModel.setCapabilityID(capability.getAccessRightsCapabilityID());
        dataModel.setSigner(requestSubjectInfo.getX509SubjectName());
        dataModel.setSignerKeyIssuer(requestSubjectInfo.getX509IssuerName());
        dataModel.setSignerKeyID(requestSubjectInfo.getX509SerialNumber().toString(16));
        return dataModel;
    }

    public static void performRequestOperation(ServiceRequestInfoModel infoModel) throws CapabilityAuthDemoRequestOptException {

        //Before perfor the operation, check if the request is valid
        checkRequestOperation(infoModel);
        //Recover request operation.
        String operation = infoModel.getOperation();
        //Recover request resource.
        String resourceID = infoModel.getResourceID();
        //Recover the file specified by the resource.
        File requestFile;
        try {
            requestFile = new File(new URI(resourceID));            
        } catch (Exception ex) {
            //The Resource is not an URi;
            LOGGER.log(Level.INFO, ex.getMessage(), ex);
            requestFile = new File(resourceID);
        }
        LOGGER.log(Level.INFO, "The Resource ID Path: {0}", requestFile.getAbsolutePath());
        //Check the operation type
        if (operation.equalsIgnoreCase(ClientServerUtils.OPERATION_UPDATE)) {
            if (!requestFile.exists()) {
                //cannot perform update operation.
                throw new CapabilityAuthDemoRequestOptException(RESPONSE_OPERATION_RESOURCE_NOT_EXIST);
            }
        }
        else if (operation.equalsIgnoreCase(ClientServerUtils.OPERATION_READ)){
            if (!requestFile.exists()) {
                //cannot perform read operation.
                throw new CapabilityAuthDemoRequestOptException(RESPONSE_OPERATION_RESOURCE_NOT_EXIST);
            }
        }
        else if (operation.equalsIgnoreCase(ClientServerUtils.OPERATION_DELETE)){
            if (!requestFile.exists()) {
                //cannot perform delete operation.
                throw new CapabilityAuthDemoRequestOptException(RESPONSE_OPERATION_RESOURCE_NOT_EXIST);
            }
            else {
                //cannot perform delete operation over directory.
                if(requestFile.isDirectory()){
                    throw new CapabilityAuthDemoRequestOptException(RESPONSE_OPERATION_DELETE_NOT_PERFORMED);
                }
                else if(requestFile.isFile()){
                    if(!requestFile.delete()){
                        //cannot perform delete operation.
                        throw new CapabilityAuthDemoRequestOptException(RESPONSE_OPERATION_DELETE_NOT_PERFORMED);
                    }
                }
                else{
                    //The resourceID is not a file.
                    throw new CapabilityAuthDemoRequestOptException(RESPONSE_OPERATION_DELETE_NOT_PERFORMED);
                }
            }
        }
        else if (operation.equalsIgnoreCase(ClientServerUtils.OPERATION_CREATE)) {
            if (requestFile.exists()) {
                //cannot perform create operation.
                throw new CapabilityAuthDemoRequestOptException(RESPONSE_OPERATION_CREATE_ALREADY_EXIST);
            }
            else {
                if (requestFile.isDirectory())
                    throw new CapabilityAuthDemoRequestOptException(RESPONSE_OPERATION_CREATE_DIRECTORY);
                try {
                    String path = requestFile.getAbsolutePath();
                    String dirPath = path.substring(0, path.lastIndexOf(File.separatorChar) + 1);
                    File dir = new File (dirPath);
                    dir.mkdirs();
                    
                    //write something to the created file
                    FileWriter fileWriter = new FileWriter(requestFile.toURI().getPath());
                    fileWriter.append(OPERATION_CREATE_FILE_CONTENT);
                    fileWriter.close();
                }
                catch (IOException ex) {
                    String response = "";
                    response += ClientServerUtils.RESPONSE_INTERNAL_ERROR;
                    response += " ";
                    response += RESPONSE_OPERATION_CREATE_NOT_PERFORMED;
                    throw new CapabilityAuthDemoRequestOptException(response, ex);
                }
            }
        }
        else{
            //due the checkRequestValidity this case should not been performed.
            throw new CapabilityAuthDemoRequestOptException(ClientServerUtils.RESPONSE_REQUEST_OPERATION_NOT_PERFORMED);
        }
    }
    
    private static void checkRequestOperation(ServiceRequestInfoModel infoModel) throws CapabilityAuthDemoRequestOptException {

        AccessRightsCapabilityType capability = infoModel.getRequestCapability();
        String capabilityResource = capability.getResourceID();
        String requestResource = infoModel.getResourceID();
        if (!capabilityResource.equals(requestResource)) {
            throw new CapabilityAuthDemoRequestOptException(RESPONSE_INVALID_RESOURCE_ID);
        }
        List<AccessRightType> accessRights = capability.getAccessRights().getAccessRight();
        String requestOperation = infoModel.getOperation();
        boolean requestOperationFound = false;
        for (AccessRightType accessRightType : accessRights) {
            String action = accessRightType.getPermittedAction().getValue();
            if (action.equalsIgnoreCase(requestOperation)) {
                requestOperationFound = true;
                break;
            }
        }
        if (!requestOperationFound) {
            throw new CapabilityAuthDemoRequestOptException(RESPONSE_INVALID_CAPABILITY_OPERATION);
        }
        boolean crudOperationFound = false;
        for(String crudOperation : ClientServerUtils.CRUD_LIST){
            if (crudOperation.equalsIgnoreCase(requestOperation)) {
                crudOperationFound = true;
                break;
            }
        }
        if (!crudOperationFound) {
            throw new CapabilityAuthDemoRequestOptException(RESPONSE_INVALID_CRUD_OPERATION);
        }
    }
}
